----------------------------------------------------------------------------------
-- Company: 
-- Engineer:            Peter Fall
-- 
-- Create Date:    12:00:04 05/31/2011 
-- Design Name: 
-- Module Name:    arp_STORE_br - Behavioral 
-- Project Name: 
-- Target Devices: 
-- Tool versions: 
-- Description:
--              ARP storage table using block ram with lookup based on IP address
--              implements upto 255 entries with sequential search
--              uses round robin overwrite when full (LRU would be better, but ...)
--
--              store may take a number of cycles and the request is latched
--              lookup may take a number of cycles. Assumes that request signals remain valid during lookup
--
-- Dependencies: 
--
-- Revision: 
-- Revision 0.01 - File Created
-- Additional Comments: 
--
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.all;
use IEEE.NUMERIC_STD.all;
use ieee.std_logic_unsigned.all;
use work.arp_types.all;

entity arp_STORE_br is
    generic (
        MAX_ARP_ENTRIES : integer := 255          -- max entries in the store
    );
    port (
        -- read signals
        read_req    : in  arp_store_rdrequest_t;  -- requesting a lookup or store
        read_result : out arp_store_result_t;     -- the result
        -- write signals
        write_req   : in  arp_store_wrrequest_t;  -- requesting a lookup or store
        -- control and status signals
        clear_store : in  std_logic;              -- erase all entries
        entry_count : out unsigned(7 downto 0);   -- how many entries currently in store
        -- system signals
        clk         : in  std_logic;
        reset       : in  std_logic
    );
end arp_STORE_br;

architecture Behavioral of arp_STORE_br is

    type st_state_t is (IDLE, PAUSE, SEARCH, FOUND, NOT_FOUND);

    type ip_ram_t is array (0 to MAX_ARP_ENTRIES) of std_logic_vector(31 downto 0);
    type mac_ram_t is array (0 to MAX_ARP_ENTRIES) of std_logic_vector(47 downto 0);
    subtype addr_t is integer range 0 to MAX_ARP_ENTRIES;

    type count_mode_t is (RST, INCR, HOLD);

    type mode_t is (MREAD, MWRITE);

    -- state variables
    signal ip_ram          : ip_ram_t;     -- will be implemented as block ram
    signal mac_ram         : mac_ram_t;    -- will be implemented as block ram     
    signal st_state        : st_state_t;
    signal next_write_addr : addr_t;       -- where to make the next write
    signal num_entries     : addr_t;       -- number of entries in the store
    signal next_read_addr  : addr_t;       -- next addr to read from
    signal entry_found     : arp_entry_t;  -- entry found in search
    signal mode            : mode_t;       -- are we writing or reading?
    signal req_entry       : arp_entry_t;  -- entry latched from req

    -- busses
    signal next_st_state   : st_state_t;
    signal arp_entry_val   : arp_entry_t;
    signal mode_val        : mode_t;
    signal write_addr      : addr_t;        -- actual write address to use
    signal read_result_int  : arp_store_result_t;

    -- control signals
    signal set_next_write_addr : count_mode_t;
    signal set_num_entries     : count_mode_t;
    signal set_next_read_addr  : count_mode_t;
    signal write_ram           : std_logic;
    signal set_entry_found     : std_logic;
    signal set_mode            : std_logic;

    function read_status(status : arp_store_rslt_t; signal mode : mode_t) return arp_store_rslt_t is
        variable ret : arp_store_rslt_t;
    begin
        case status is
          when IDLE => ret := status;
          when others =>
            if mode = MWRITE then
                ret := BUSY;
            else
                ret := status;
            end if;
        end case;
        return ret;
    end read_status;

begin

combinatorial : process (
    -- input signals
    read_req, write_req, clear_store, reset,
    -- state variables
    ip_ram, mac_ram, st_state, next_write_addr, num_entries,
    next_read_addr, entry_found, mode, req_entry,
    -- busses
    next_st_state, arp_entry_val, mode_val, write_addr, read_result_int, 
    -- control signals
    set_next_write_addr, set_num_entries, set_next_read_addr, set_entry_found,
    write_ram, set_mode
    )
begin
    -- set output followers
    read_result_int.status <= IDLE;
    read_result_int.entry  <= entry_found;
    entry_count        <= to_unsigned(num_entries, 8);

    -- set bus defaults
    next_st_state <= st_state;
    mode_val      <= MREAD;
    write_addr    <= next_write_addr;

    -- set signal defaults
    set_next_write_addr <= HOLD;
    set_num_entries     <= HOLD;
    set_next_read_addr  <= HOLD;
    write_ram           <= '0';
    set_entry_found     <= '0';
    set_mode            <= '0';

    -- STORE FSM
    case st_state is
      when IDLE =>
        if write_req.req = '1' then
            -- need to search to see if this IP already there
            set_next_read_addr <= RST;    -- start lookup from beginning
            mode_val           <= MWRITE;
            set_mode           <= '1';
            next_st_state      <= PAUSE;
        elsif read_req.req = '1' then
            set_next_read_addr <= RST;    -- start lookup from beginning
            mode_val           <= MREAD;
            set_mode           <= '1';
            next_st_state      <= PAUSE;
        end if;

      when PAUSE =>
        -- wait until read addr is latched and we get first data out of the ram
        read_result_int.status <= read_status(BUSY, mode);
        set_next_read_addr <= INCR;
        next_st_state      <= SEARCH;

      when SEARCH =>
        read_result_int.status <= read_status(SEARCHING, mode);
        -- check if have a match at this entry
        if req_entry.ip = arp_entry_val.ip and next_read_addr <= num_entries then
            -- found it
            set_entry_found <= '1';
            next_st_state   <= FOUND;
        elsif next_read_addr > num_entries or next_read_addr >= MAX_ARP_ENTRIES then
            -- reached end of entry table
            read_result_int.status <= read_status(NOT_FOUND, mode);
            next_st_state      <= NOT_FOUND;
        else
            -- no match at this entry , go to next
            set_next_read_addr <= INCR;
        end if;

      when FOUND =>
        read_result_int.status <= read_status(FOUND, mode);
        if mode = MWRITE then
            write_addr    <= next_read_addr - 1;
            write_ram     <= '1';
            next_st_state <= IDLE;
        elsif read_req.req = '0' then   -- wait in this state until request de-asserted
            next_st_state <= IDLE;
        end if;

      when NOT_FOUND =>
        read_result_int.status <= read_status(NOT_FOUND, mode);
        if mode = MWRITE then
            -- need to write into the next free slot
            write_addr          <= next_write_addr;
            write_ram           <= '1';
            set_next_write_addr <= INCR;
            if num_entries < MAX_ARP_ENTRIES then
                -- if not full, count another entry (if full, it just wraps)
                set_num_entries <= INCR;
            end if;
            next_st_state <= IDLE;
        elsif read_req.req = '0' then   -- wait in this state until request de-asserted
            next_st_state <= IDLE;
        end if;

    end case;
end process;

sequential : process (clk)
begin
    if rising_edge(clk) then
        -- ram processing
        if write_ram = '1' then
            ip_ram(write_addr)  <= req_entry.ip;
            mac_ram(write_addr) <= req_entry.mac;
        end if;
        if next_read_addr < MAX_ARP_ENTRIES then
            arp_entry_val.ip  <= ip_ram(next_read_addr);
            arp_entry_val.mac <= mac_ram(next_read_addr);
        else
            arp_entry_val.ip  <= (others => '0');
            arp_entry_val.mac <= (others => '0');
        end if;

        read_result <= read_result_int;

        if reset = '1' or clear_store = '1' then
            -- reset state variables
            st_state        <= IDLE;
            next_write_addr <= 0;
            num_entries     <= 0;
            next_read_addr  <= 0;
            entry_found.ip  <= (others => '0');
            entry_found.mac <= (others => '0');
            req_entry.ip    <= (others => '0');
            req_entry.mac   <= (others => '0');
            mode            <= MREAD;

        else
            -- Next req_state processing
            st_state <= next_st_state;

            -- mode setting and write request latching
            if set_mode = '1' then
                mode <= mode_val;
                if mode_val = MWRITE then
                    req_entry <= write_req.entry;
                else
                    req_entry.ip  <= read_req.ip;
                    req_entry.mac <= (others => '0');
                end if;
            end if;

            -- latch entry found
            if set_entry_found = '1' then
                entry_found <= arp_entry_val;
            end if;

            -- next_write_addr counts and wraps
            case set_next_write_addr is
              when HOLD => next_write_addr <= next_write_addr;
              when RST  => next_write_addr <= 0;
              when INCR => 
                if next_write_addr < MAX_ARP_ENTRIES-1 then 
                    next_write_addr <= next_write_addr + 1; 
                else 
                    next_write_addr <= 0; 
                end if;
            end case;

            -- num_entries counts and holds at max
            case set_num_entries is
              when HOLD => num_entries  <= num_entries;
              when RST  => num_entries  <= 0;
              when INCR => 
                if next_write_addr < MAX_ARP_ENTRIES then 
                    num_entries <= num_entries + 1; 
                end if;
            end case;

            -- next_read_addr counts and wraps
            case set_next_read_addr is
              when HOLD => next_read_addr  <= next_read_addr;
              when RST  => next_read_addr <= 0;
              when INCR => 
                if next_read_addr < MAX_ARP_ENTRIES then 
                    next_read_addr <= next_read_addr + 1; 
                else 
                    next_read_addr <= 0; 
                end if;
            end case;

        end if;
    end if;
end process;

end Behavioral;
